Bank.java
1: // Bank.java - A demo of (un)synchronized threads.
2: // This program starts two threads, one that transfer $100 from
3: // checking to savings and back again. The other transfers $10
4: // back and forth. If the "synchronized" keyword is commented
5: // out from the transfer() method, the balances will eventually
6: // be wrong and the demo will stop.
7: //
8: // Written 2000 by Wayne Pollock, Tampa Florida USA.
9: // Updated 5/2021: Converted from Applet, updated style.
10:
11: import java.awt.*;
12: import java.awt.event.*;
13:
14: public class Bank extends Frame implements Runnable, ActionListener {
15: private Account checking, savings;
16: private volatile boolean demoRunning;
17: private static final int totalBal = 1000;
18: private volatile Thread t1, t2;
19: private static final int t1TransferAmount = 100;
20: private static final int t2TransferAmount = 10;
21:
22: private Button btn;
23: private TextField checkingTF, savingsTF;
24: private Panel errorP;
25:
26: public static void main ( String[] args ) {
27: Frame f = new Bank();
28: f.setTitle( "Banking Transfer Demo" );
29: f.setSize(430, 260);
30: f.setLocationRelativeTo( null );
31: f.addWindowListener( new WindowAdapter() {
32: @Override public void windowClosing( WindowEvent we ) {
33: System.exit(0);
34: }
35: });
36: f.setVisible( true );
37: }
38:
39: public Bank () {
40: checking = new Account( totalBal / 2 );
41: savings = new Account( totalBal - checking.bal );
42: demoRunning = false;
43:
44: checkingTF = new TextField( 7 );
45: checkingTF.setEditable( false );
46: checkingTF.setText( " $" + checking.bal );
47: checkingTF.setFont( new Font("Monospaced", Font.PLAIN, 14) );
48:
49: savingsTF = new TextField( 7 );
50: savingsTF.setEditable( false );
51: savingsTF.setText( " $" + savings.bal );
52: savingsTF.setFont( new Font("Monospaced", Font.PLAIN, 14) );
53:
54: btn = new Button( "Start" ); // This is also the stop button.
55: btn.setBackground( Color.green.brighter() );
56:
57: Label warning = new Label( "Balance Error!", Label.CENTER );
58: warning.setFont( new Font( "SansSerif", Font.BOLD, 24 ) );
59: warning.setForeground( Color.red );
60:
61: Label title = new Label( "(Un)Synchronized Thread Demo",
62: Label.CENTER );
63: title.setFont( new Font( "SansSerif", Font.BOLD, 24 ) );
64:
65: Label footer = new Label( "\u00A9 2021 by Wayne Pollock, "
66: + "Tampa FL USA. All Rights Reserved.", Label.CENTER );
67: footer.setFont( new Font( "SansSeriff", Font.PLAIN, 10 ) );
68:
69: // Layout the components:
70: setLayout( new BorderLayout() );
71: setBackground( Color.lightGray );
72: Panel wrapper; // Used so buttons and things won't stretch.
73: add( title, "North" );
74: Panel checkingP = new Panel();
75: checkingP.setLayout( new BorderLayout() );
76: checkingP.add( new Label( "Checking Bal", Label.CENTER ),
77: "North" );
78: checkingP.add( checkingTF, "South" );
79: wrapper = new Panel();
80: wrapper.add( checkingP );
81: add( wrapper, "West" );
82:
83: Panel savingsP = new Panel();
84: savingsP.setLayout( new BorderLayout() );
85: savingsP.add( new Label( "Savings Bal", Label.CENTER ),
86: "North" );
87: savingsP.add( savingsTF, "South" );
88: wrapper = new Panel();
89: wrapper.add( savingsP );
90: add( wrapper, "East" );
91:
92: errorP = new Panel();
93: errorP.setLayout( new BorderLayout() );
94: errorP.setVisible( false );
95: errorP.add( new Label( " " ), "North" ); // A Spacer.
96: errorP.add( warning, "Center" );
97: Button resetBtn = new Button( " Reset " );
98: resetBtn.setBackground( Color.orange );
99: wrapper = new Panel();
100: wrapper.add( resetBtn );
101: errorP.add( wrapper, "South" );
102: add( errorP, "Center" );
103:
104: Panel bot = new Panel();
105: bot.setLayout( new BorderLayout() );
106: wrapper = new Panel();
107: wrapper.add( btn );
108: bot.add( wrapper, "North" );
109: bot.add( new Label( " " ), "Center" ); // A spacer.
110: bot.add( footer, "South" );
111: add( bot, "South" );
112: errorP.requestFocus(); // Hide the focus.
113:
114: // Hook up event listeners:
115: btn.addActionListener( this );
116: resetBtn.addActionListener( ( ActionEvent e ) -> { reset(); } );
117: start(); // Create and run the two threads
118: }
119:
120: public void start () {
121: t1 = new Thread( this, "thread1" );
122: t2 = new Thread( this, "thread2" );
123: t1.start();
124: t2.start();
125: }
126:
127: public void stop () {
128: t1 = t2 = null; // The threads will check this and die.
129: }
130:
131: private void reset () {
132: errorP.setVisible( false );
133: checking.bal = totalBal / 2;
134: savings.bal = totalBal - checking.bal;
135: checkingTF.setText( " $" + checking.bal );
136: savingsTF.setText( " $" + savings.bal );
137: btn.setLabel( "Start" );
138: btn.setBackground( Color.green.brighter() );
139: btn.setEnabled( true );
140: errorP.requestFocus(); // Hide the focus.
141: demoRunning = false;
142: start();
143: }
144:
145: public synchronized void actionPerformed ( ActionEvent e ) {
146: if ( demoRunning ) { // then turn it off:
147: btn.setLabel( "Start" );
148: btn.setBackground( Color.green.brighter() );
149: } else {
150: btn.setLabel( "Stop " );
151: btn.setBackground( Color.red );
152: }
153:
154: // Threads will check this flag and suspend themselves:
155: demoRunning = ! demoRunning;
156: notifyAll(); // Wake up sleeping threads.
157: errorP.requestFocus(); // Hide the focus.
158: }
159:
160: public void run () {
161: Thread me = Thread.currentThread();
162: int amount;
163: boolean toChecking = true; // Which way to transfer funds.
164:
165: if ( me == t1 )
166: amount = t1TransferAmount;
167: else
168: amount = t2TransferAmount;
169:
170: while ( me == t1 || me == t2 ) { // else time to terminate!
171: try { // Pause for 0.1 seconds up to 1.0 seconds:
172: Thread.sleep( (int) ( Math.random() * 900 ) + 100 );
173:
174: // Check if demo is suspended (or terminated) yet:
175: synchronized( this ) {
176: while ( !demoRunning && ( me == t1 || me == t2 ) )
177: wait();
178: }
179: }
180: catch ( InterruptedException ignored ) { }
181:
182: if ( me != t1 && me != t2 ) // Check for terminate again.
183: break;
184:
185: if ( toChecking )
186: transfer( checking, savings, amount );
187: else
188: transfer( savings, checking, amount );
189:
190: toChecking = ! toChecking;
191:
192: synchronized( this ) {
193: checkingTF.setText( " $" + checking.bal );
194: savingsTF.setText( " $" + savings.bal );
195: if ( checking.bal + savings.bal != totalBal ) {
196: errorP.setVisible( true );
197: stop();
198: btn.setEnabled( false ); // Disable Start/Stop btn
199: validate();
200: }
201: }
202: }
203: }
204:
205: // Try the effect of commenting out "synchronized"!
206: // (The sleep statement just increases the probability of error.)
207:
208: private /*synchronized*/ void transfer ( Account toAccount,
209: Account fromAccount,
210: int amount )
211: {
212: int bal = fromAccount.bal;
213: pause();
214: bal -= amount;
215: fromAccount.bal = bal;
216:
217: bal = toAccount.bal;
218: pause();
219: bal += amount;
220: toAccount.bal = bal;
221: }
222:
223: private void pause () {
224: if ( Math.random() < 0.35 )
225: try { Thread.sleep( 10 ); } catch ( Exception e ) {}
226: }
227:
228: private static class Account {
229: public int bal;
230:
231: Account ( int initialBalance )
232: { bal = initialBalance; }
233: }
234:
235: } // End of class Bank